﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CodeDom;

namespace System.Windows.Forms.TestUtilities;

public static class CodeDomHelpers
{
    public static void AssertEqualCodeStatementCollection(CodeStatementCollection expected, CodeStatementCollection actual)
    {
        try
        {
            Assert.Equal(expected.Count, actual.Count);
            for (int i = 0; i < expected.Count; i++)
            {
                Assert.Equal(GetConstructionString(expected[i]), GetConstructionString(actual[i]));
            }
        }
        catch (Xunit.Sdk.XunitException)
        {
            Console.WriteLine($"Expected: {expected.Count} elements");
            for (int i = 0; i < expected.Count; i++)
            {
                Console.WriteLine($"- [{i}] {GetConstructionString(expected[i])}");
            }

            Console.WriteLine("");

            Console.WriteLine($"Actual: {actual.Count} elements");
            for (int i = 0; i < actual.Count; i++)
            {
                Console.WriteLine($"- [{i}] {GetConstructionString(actual[i])}");
            }

            throw;
        }
    }

    public static string GetConstructionString(CodeObject o)
    {
        if (o is CodeStatement cs)
        {
            if (cs.StartDirectives.Count != 0 || cs.EndDirectives.Count != 0)
            {
                throw new NotImplementedException("Directives not supported.");
            }
        }

        switch (o)
        {
            case CodeVariableDeclarationStatement v:
                if (v.InitExpression is null)
                {
                    return $"new CodeVariableDeclarationStatement({GetType(v.Type)}, {GetString(v.Name)});";
                }

                return $"new CodeVariableDeclarationStatement({GetType(v.Type)}, {GetString(v.Name)}, {GetConstructionString(v.InitExpression)});";
            case CodeAssignStatement cas:
                return $"new CodeAssignStatement({GetConstructionString(cas.Left)}, {GetConstructionString(cas.Right)})";
            case CodeVariableReferenceExpression cvre:
                return $"new CodeVariableReferenceExpression({GetString(cvre.VariableName)})";
            case CodeObjectCreateExpression coce:
                {
                    if (coce.Parameters.Count == 0)
                    {
                        return $"new CodeObjectCreateExpression({GetType(coce.CreateType)})";
                    }

                    string parameters = string.Join(", ", coce.Parameters.Cast<CodeObject>().Select(o => GetConstructionString(o)));
                    return $"new CodeObjectCreateExpression({GetType(coce.CreateType)}, {parameters})";
                }

            case CodeCommentStatement ccs:
                if (ccs.Comment.DocComment)
                {
                    return $"new CodeCommentStatement({GetString(ccs.Comment.Text)}, true)";
                }

                return $"new CodeCommentStatement({GetString(ccs.Comment.Text)})";
            case CodePropertyReferenceExpression cpre:
                return $"new CodePropertyReferenceExpression({GetConstructionString(cpre.TargetObject)}, {GetString(cpre.PropertyName)})";
            case CodePrimitiveExpression cpe:
                {
                    if (cpe.Value is null)
                    {
                        return "new CodePrimitiveExpression(null)";
                    }
                    else if (cpe.Value is string s)
                    {
                        return $"new CodePrimitiveExpression({GetString(s)})";
                    }

                    return $"new CodePrimitiveExpression({cpe.Value})";
                }

            default:
                throw new NotImplementedException(o.ToString());
        }

        string GetString(string s)
        {
            if (s is null)
            {
                return "null";
            }
            else if (s.Length == 0)
            {
                return "string.Empty";
            }

            return $"\"{s}\"";
        }

        string GetType(CodeTypeReference reference)
        {
            Type result = Type.GetType(reference.BaseType);
            if (result is not null)
            {
                return $"typeof({result.Name})";
            }

            return GetString(reference.BaseType);
        }
    }
}
